package com.katesoft.scale4j.persistent.model.unified;

import com.katesoft.scale4j.log.Logger;
import com.katesoft.scale4j.log.LogFactory;
import org.hibernate.Session;
import org.hibernate.collection.PersistentCollection;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.envers.AuditReader;
import org.hibernate.envers.entities.mapper.relation.lazy.proxy.CollectionProxy;
import org.hibernate.envers.entities.mapper.relation.lazy.proxy.MapProxy;
import org.hibernate.proxy.HibernateProxy;

import javax.persistence.MappedSuperclass;
import java.util.Date;

import static com.katesoft.scale4j.persistent.utils.DataAccessUtility.initialRevision;
import static com.katesoft.scale4j.persistent.utils.DataAccessUtility.latestModifyRevision;
import static org.hibernate.criterion.Restrictions.eq;
import static org.hibernate.envers.AuditReaderFactory.get;

/**
 * abstract parent for all domain classes defined in jvmcluster module.
 * <p/>
 * Clients should use this class instead of inheritance from BO.
 *
 * @author kate2007
 */
@MappedSuperclass
public abstract class AbstractPersistentEntity extends BO
{
    private static final Logger LOGGER = LogFactory.getLogger(AbstractPersistentEntity.class);

    /** @return detached criteria pre-populated with identifier and version, further criteria restrictions are expected. */
    public DetachedCriteria entityForUpdateCriteria()
    {
        DetachedCriteria criteria = DetachedCriteria.forClass(getClass());
        criteria.add(eq(PROP_UID, getUniqueIdentifier()));
        criteria.add(eq(PROP_VERSION, getVersion()));
        criteria.add(eq(PROP_GUID, getGlobalUniqueIdentifier()));
        return criteria;
    }

    /** @return clone of this object(pre-populated with unique identifier and version) that can be used with find-by-example  hibernate method. */
    public AbstractPersistentEntity entityForUpdateExample()
    {
        AbstractPersistentEntity clone = (AbstractPersistentEntity) this.clone();
        clone.cleanBeanProperties();
        clone.setUniqueIdentifier(getUniqueIdentifier());
        clone.setVersion(getVersion());
        clone.setGlobalUniqueIdentifier(getGlobalUniqueIdentifier());
        return clone;
    }

    /**
     * looking for creation date of this entity using audit data.
     *
     * @param session hibernate session
     * @return creation timestamp
     */
    public Date getCreationTimestamp(Session session)
    {
        AuditReader auditReader = get(session);
        Number revision = initialRevision(auditReader, getPersistentClass(), getUniqueIdentifier());
        if (revision == null) {
            LOGGER.warn("unable to find creation_date for entity[class=%s, unique_identifier=%s], make sure you committed transaction",
                        getPersistentClass().getName(), getUniqueIdentifier());
            return null;
        }
        return auditReader.getRevisionDate(revision);
    }

    /**
     * looking for last update timestamp of this entity using audit data.
     * <p/>
     * NOTE: only modify and delete revision types are included into search criteria, so if entity was not modified(updated/deleted) at all,
     * null result will be
     * returned.
     *
     * @param session hibernate session
     * @return last update timestamp if available
     */
    public Date getLastModifyTimestamp(Session session)
    {
        AuditReader auditReader = get(session);
        Number revision = latestModifyRevision(auditReader, getPersistentClass(), getUniqueIdentifier());
        if (revision == null) {
            LOGGER.debug("unable to find last_modify_date for entity[class=%s, unique_identifier=%s], make sure you committed transaction or " +
                         "updated/deleted entity at least one time", getPersistentClass().getName(), getUniqueIdentifier());
            return null;
        }
        return auditReader.getRevisionDate(revision);
    }

    /**
     * Force initialization of a proxy or persistent collection. This method is similar to {@link org.hibernate.Hibernate#initialize(Object)},
     * but allows you to
     * initializeProxy historical data as well.
     * <p/>
     * So clients should prefer this method instead of using {@link org.hibernate.Hibernate#initialize(Object)}.
     *
     * @param proxy any proxy object
     */
    protected static void initializeProxy(Object proxy)
    {
        if (proxy == null) { return; }
        if (proxy instanceof HibernateProxy) {
            ((HibernateProxy) proxy).getHibernateLazyInitializer().initialize();
        }
        else if (proxy instanceof PersistentCollection) {
            ((PersistentCollection) proxy).forceInitialization();
        }
        // TODO initialize collection proxy and map proxy (JIRA created for this)
        // revision data - didn't found easy way to initialize
        if (proxy instanceof CollectionProxy) {
            ((CollectionProxy) proxy).size();
        }
        else if (proxy instanceof MapProxy) {
            ((MapProxy) proxy).size();
        }
    }
}
